package com.chinazyjr.framework.web.accessControl;

import java.io.Closeable;
import java.text.DecimalFormat;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;

import org.jboss.netty.util.internal.ConcurrentHashMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.zhongying.jf.util.redis.RedisUtils;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.Response;
import redis.clients.jedis.Transaction;

public class RequestRateLimitHandler {

    private static final Logger LOGGER = LoggerFactory.getLogger("LOGGER.WEB_API_REQUEST");

    private RequestRateLimitConfig config;

    private ScheduledExecutorService service = null;

    private Map<String, Runnable> runnableThreads = new ConcurrentHashMap<>();

    private Map<String, Future<?>> executiveFutures = new ConcurrentHashMap<>();

    public RequestRateLimitHandler(RequestRateLimitConfig config) {
        this.config = config;
        if (config != null && config.getStatus() != null && config.getStatus() == 1) {
            for (int i = 0; i < config.getThreads(); i++) {
                runnableThreads.put(config.getStrategy() + "_" + i + "_" + System.currentTimeMillis(), createHandler());
            }
        }
    }

    public int execute() {
        Set<Entry<String, Runnable>> entrySet = runnableThreads.entrySet();
        int executeNum = 0;
        service = Executors.newScheduledThreadPool(entrySet.size() + 2);
        for (Entry<String, Runnable> entry : entrySet) {
            String threadName = entry.getKey();
            ScheduledFuture<?> future = service.scheduleAtFixedRate(entry.getValue(), TimeUnit.SECONDS.toMillis(5),
                    config.getRate(), TimeUnit.MILLISECONDS);
            executiveFutures.put(threadName, future);
            LOGGER.info(String.format(
                    "request_rate handler \ncreate handler task... \nexecute strategy：%s \nthread: %s \nperiod: %s \nlength: %s ",
                    config.getStrategy(), threadName, config.getPeriod(), config.getLength()));
            executeNum++;
        }
        return executeNum;
    }

    public void shutdown() {
        for (Entry<String, Future<?>> entry : executiveFutures.entrySet()) {
            Future<?> future = entry.getValue();
            if (!future.isCancelled()) {
                try {
                    boolean isCancel = future.cancel(true);
                    LOGGER.info("request_rate handler cancel strategy:{} |thread:{} |success:{}", config.getStrategy(),
                            entry.getKey(), isCancel);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
        runnableThreads.clear();
        executiveFutures.clear();
        service.shutdown();
    }

    public void statusCheck() {
        if (executiveFutures == null || executiveFutures.size() == 0) {
            LOGGER.info("request_rate handler no executive futures strategy:{}", config.getStrategy());
        }
        for (Entry<String, Future<?>> entry : executiveFutures.entrySet()) {
            Future<?> future = entry.getValue();
            LOGGER.info("request_rate handler status_check strategy:{} |thread:{} |done:{} |cancelled:{}",
                    config.getStrategy(), entry.getKey(), future.isDone(), future.isCancelled());
        }

    }

    public Boolean isLive() {
        if (service == null || service.isShutdown()) {
            return false;
        }
        Set<Entry<String, Future<?>>> set = executiveFutures.entrySet();
        for (Entry<String, Future<?>> entry : set) {
            Future<?> future = entry.getValue();
            if (future != null && !future.isDone()) {
                return true;
            }
        }
        return false;
    }

    public Runnable createHandler() {
        return new Runnable() {
            @Override
            public void run() {
                ConcurrentLinkedQueue<String> queue = RequestRateLimitManager.getQueue(config.getStrategy());
                Map<String, Integer> temps = new HashMap<>();
                while (!queue.isEmpty()) {
                    String string = queue.poll();
                    if (temps.get(string) == null) {
                        temps.put(string, 1);
                    } else {
                        temps.put(string, temps.get(string) + 1);
                    }
                }
                Set<Entry<String, Integer>> entrySet = temps.entrySet();
                for (Entry<String, Integer> entry : entrySet) {
                    Calendar current = Calendar.getInstance();
                    asynCheck(entry.getKey(), hincrby(entry.getKey(), current, entry.getValue()), current);
                }
            }
        };
    }

    public String getCurrentSection(Calendar c) {
        Integer period = config.getPeriod();
        Integer length = config.getLength();
        Integer part = 1 * length;
        switch (period) {
        case 13:
            part = part * 1;
            break;
        case 12:
            part = part * 60;
            break;
        case 10:
            part = part * 60 * 60;
            break;
        default:
            part = part * 24 * 60 * 60;
            break;
        }
        return String.format("%s_%s", new DecimalFormat("00").format(period),
                new DecimalFormat("0000000000").format(c.getTimeInMillis() / 1000 / part));
    }

    public Long nextSection(Calendar c) {
        Integer period = config.getPeriod();
        Integer length = config.getLength();
        Integer part = 1 * length;
        switch (period) {
        case 13:
            part = part * 1;
            break;
        case 12:
            part = part * 60;
            break;
        case 10:
            part = part * 60 * 60;
            break;
        default:
            part = part * 24 * 60 * 60;
            break;
        }
        return c.getTimeInMillis() / 1000 / part * part + part;
    }

    public Long hincrby(String userCode, Calendar current, Integer number) {
        Jedis jedis = null;
        Transaction tx = null;
        String currentSectionStr = getCurrentSection(current);
        Long nextSection = nextSection(current);
        try {
            jedis = RedisUtils.getClient();
            tx = jedis.multi();
            Response<Long> response = tx.hincrBy(RequestRateLimitManager.REQUEST_RATE_REDIS_REQUEST_TIMES
                    + config.getStrategy() + "_" + currentSectionStr, userCode, number);
            tx.expireAt(RequestRateLimitManager.REQUEST_RATE_REDIS_REQUEST_TIMES + config.getStrategy() + "_"
                    + getCurrentSection(current), nextSection);
            tx.exec();
            return response.get();
        } catch (Exception e) {
            tx.discard();
            LOGGER.error("error", e);
        } finally {
            autoClose(tx, jedis);
        }
        return 0L;
    }

    public boolean asynCheck(String userCode, Long times, Calendar current) {
        if (times > config.getLimit()) {
            Jedis jedis = null;
            Transaction tx = null;
            String currentSectionStr = getCurrentSection(current);
            Long nextSection = nextSection(current);
            try {
                jedis = RedisUtils.getClient();
                tx = jedis.multi();
                tx.hset(RequestRateLimitManager.REQUEST_RATE_REDIS_BLACK_LIST + config.getStrategy() + "_"
                        + currentSectionStr, userCode, currentSectionStr);
                tx.expireAt(RequestRateLimitManager.REQUEST_RATE_REDIS_BLACK_LIST + config.getStrategy() + "_"
                        + getCurrentSection(current), nextSection);
                tx.exec();
                LOGGER.info("request_rate handler join black_list:{} times:{}", userCode, times);
            } catch (Exception e) {
                tx.discard();
                LOGGER.error("error", e);
            } finally {
                autoClose(tx, jedis);
            }
            return false;
        }

        return true;
    }

    public Boolean pass(String userCode) {
        String currentSectionStr = getCurrentSection(Calendar.getInstance());
        Jedis jedis = null;
        try {
            jedis = RedisUtils.getClient();
            String value = jedis.hget(RequestRateLimitManager.REQUEST_RATE_REDIS_BLACK_LIST + config.getStrategy() + "_"
                    + currentSectionStr, userCode);
            if (value != null) {
                return false;
            }
        } finally {
            autoClose(jedis);
        }
        return true;
    }

    public static void autoClose(Closeable... closeable) {
        for (Closeable ele : closeable) {
            try {
                if (ele != null) {
                    ele.close();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}
